using System;
using System.Collections.Generic;
using System.Linq;
using FireStarter.Base;
using Firestarter.DB.Schema.BO;
using FireStarterModeller.BO;
using FireStarterModeller.Logic;
using Habanero.Base;
using Habanero.BO;
using Habanero.BO.ClassDefinition;

namespace Firestarter.DB_DomainModel.Logic
{
    public class DBToDMRelationshipImporter : IDBToDMRelationshipImporter
    {
        public DBToDMRelationshipImporter(IRelationshipNameGenerator relationshipNameGenerator, IDefClassFactory classFactory)
        {
//            FMDefClassFactory factory = new FMDefClassFactory();
            if (relationshipNameGenerator == null) throw new ArgumentNullException("relationshipNameGenerator");
            if (classFactory == null) throw new ArgumentNullException("classFactory");
            RelationshipNameGenerator = relationshipNameGenerator;
            ClassFactory = classFactory;
        }       
        public DBToDMRelationshipImporter(IRelationshipNameGenerator relationshipNameGenerator)
        {
            if (relationshipNameGenerator == null) throw new ArgumentNullException("relationshipNameGenerator");
            RelationshipNameGenerator = relationshipNameGenerator;
        }

        public IRelationshipNameGenerator RelationshipNameGenerator { get; private set; }
        private IDefClassFactory _classFactory;
        public IDefClassFactory ClassFactory
        {
            get { return _classFactory ?? (_classFactory = new FMDefClassFactory()); }
            set { _classFactory = value; }
        }

        public DMRelationship Import(DBForeignKey dbForeignKey, out IList<string> errorMessages)
        {
            if (dbForeignKey == null) throw new ArgumentNullException("dbForeignKey");
            errorMessages = new List<string>(4);
            DMClass parentDMClass = dbForeignKey.ParentDMClass() as DMClass;
            DMClass relatedDMClass = dbForeignKey.RelatedDMClass() as DMClass;
            string standardStringMessage = CreateStandardErrorMessage(dbForeignKey);
            if(parentDMClass == null || relatedDMClass == null)
            {
                string expectedMessage = standardStringMessage + " since it does not have a related or owner Class";
                errorMessages.Add(expectedMessage);
                return null;
            }
            Validate(dbForeignKey, errorMessages);

            var dmRelationship = GetMappedRelationship(dbForeignKey);


            foreach (DMRelationshipProperty relationshipProperty in GetBusinessObjectArray(dmRelationship.RelationshipProperties))
            {
                var fkColumn = FindForeignKeyColumn(dbForeignKey.ForeignKeyColumns, relationshipProperty);
                if (fkColumn == null || ApprovedDelete(fkColumn))
                {
                    relationshipProperty.MarkForDelete();
                }
            }

            foreach (DBForeignKeyColumn keyColumn in GetBusinessObjectArray(dbForeignKey.ForeignKeyColumns))
            {
                var parentColumn = keyColumn.ParentColumn;
                var relatedColumn = keyColumn.RelatedColumn;
                var checkColumns = CheckColumns(parentDMClass, dbForeignKey, keyColumn, errorMessages);
                if (!checkColumns) continue;

                var relProp = FindRelationshipProperty(dmRelationship.RelationshipProperties, keyColumn);
                if (relProp == null && !ApprovedDelete(keyColumn))
                {
                    DMProperty parentProp = parentColumn == null? null: parentColumn.FindBOProperty(parentDMClass);
                    var relatedProp = relatedColumn == null ? null : relatedColumn.FindBOProperty(relatedDMClass);
                    var checkProps = CheckProps(dbForeignKey, parentDMClass, errorMessages, parentColumn, relatedColumn, parentProp, relatedProp);
                    if (!checkProps) continue;
                    
                    DMRelationshipProperty relPropDef = CreateRelationshipProperty(parentProp, relatedProp);
                    dmRelationship.RelationshipProperties.Add(relPropDef);
                    keyColumn.PendingChangeType = ChangeType.None;
                }
                if (ApprovedDelete(keyColumn))
                {
                    keyColumn.MarkForDelete();
                    if (relProp != null)
                    {
                        var relPropDeleter = new DMRelationshipPropertyDeleter(relProp);
                        relPropDeleter.Delete();
                    }
                }
                keyColumn.PendingChangeType = ChangeType.None;
            }


            if (String.IsNullOrEmpty(dmRelationship.RelationshipName))
            {
                dmRelationship.RelationshipName = RelationshipNameGenerator.CreateRelationshipName(dmRelationship);
/*                Console.WriteLine(string.Format("RelationshipCreated For {4} - {0} '{1}' '{2}' Related To '{3}'", 
                        dmRelationship.RelationshipID, dmRelationship.RelationshipName, dmRelationship.Cardinality, 
                        dmRelationship.RelatedClassName, dmRelationship.OwnerClassName));*/
            }
            if (String.IsNullOrEmpty(dmRelationship.ReverseRelationship.RelationshipName))
            {
                dmRelationship.ReverseRelationship.RelationshipName = RelationshipNameGenerator.CreateRelationshipName(dmRelationship.ReverseRelationship);
            }
            return dmRelationship;
        }

        private bool CheckProps(DBForeignKey dbForeignKey, DMClass parentDMClass, IList<string> errorMessages, DBColumn parentColumn, DBColumn relatedColumn, DMProperty parentProp, DMProperty relatedProp)
        {
            if (parentProp == null)
            {
                AppendPropMissingErrMessage(parentDMClass, dbForeignKey, parentColumn, errorMessages);
                return true;
            }
            if (relatedProp == null)
            {
                AppendPropMissingErrMessage(parentDMClass, dbForeignKey, relatedColumn, errorMessages);
                return true;//This seems strange but if Rel Prop is missing then can still import rel will just be in an invalid state.
            }
            return true;
        }

        private bool CheckColumns(DMClass parentDMClass, DBForeignKey dbForeignKey, DBForeignKeyColumn keyColumn, IList<string> errorMessages)
        {
            var parentColumn = keyColumn.ParentColumn;
            var relatedColumn = keyColumn.RelatedColumn;
            if (parentColumn == null)
            {
                AppendColumnMissingErrMessage(parentDMClass, dbForeignKey, errorMessages);
                return true;
            }
            if (relatedColumn == null)
            {
                AppendColumnMissingErrMessage(parentDMClass, dbForeignKey, errorMessages);
                return true;
            }
            return true;
        }

        private static void AppendPropMissingErrMessage(DMClass dmClass, DBForeignKey dbKey, DBColumn column, IList<string> errorMessages)
        {
            string expectedMessage = string.Format("The Foreign Key '{0}' for business object '{1}' could not be "
                                                   + "imported since the Property for Column '{2}' does not exist on the DMClass",
                                                   dbKey.ForeignKeyName, dmClass.ClassNameBO, column == null? "": column.ColumnName);

            errorMessages.Add(expectedMessage);

        }
        private static void AppendColumnMissingErrMessage(DMClass dmClass, DBForeignKey dbKey,  IList<string> errorMessages)
        {
            string expectedMessage = string.Format("The Foreign Key '{0}' for business object '{1}' could not be "
                                                   + "imported since the column that relates to the foreign key '{0}' column does not exist",
                                                   dbKey.ForeignKeyName, dmClass.ClassNameBO);

            errorMessages.Add(expectedMessage);

        }
        private IBusinessObject[] GetBusinessObjectArray(IBusinessObjectCollection fromBOCol)
        {
            IBusinessObject[] businessObjects = new IBusinessObject[fromBOCol.Count];
            fromBOCol.CopyTo(businessObjects, 0);
            return businessObjects;
        }
        private void Validate(DBForeignKey dbForeignKey, IList<string> errorMessages)
        {
            string standardStringMessage = CreateStandardErrorMessage(dbForeignKey);
            if (dbForeignKey.ForeignKeyColumns.Count == 0)
            {
                string expectedMessage = standardStringMessage + " since it does not have any columns";
                errorMessages.Add(expectedMessage);
            }

            if (dbForeignKey.ParentDMClass() == null)
            {
                string expectedMessage = standardStringMessage + " since the owner Class cannot be determined";
                errorMessages.Add(expectedMessage);
            }
            if (dbForeignKey.RelatedDMClass() == null)
            {
                string expectedMessage = standardStringMessage + " since the related Class cannot be determined";
                errorMessages.Add(expectedMessage);
            }

            DBForeignKeyColumn[] foreignKeyColumnInfos = dbForeignKey.ForeignKeyColumns.ToArray();
            foreach (var foreignKeyColumn in foreignKeyColumnInfos)
            {
                ValidateFKColumns(foreignKeyColumn, errorMessages);
            }
        }

        public void DeleteAllRelPropsWithNoFKColumn(IEnumerable<IDMRelationshipProperty> relationshipProperties, IEnumerable<DBForeignKeyColumn> foreignKeyColumns)
        {
            var relPropsToBeDeleted = from relProp in relationshipProperties
                                           let findForeignKeyColumn = FindForeignKeyColumn(foreignKeyColumns, relProp)
                                           where findForeignKeyColumn == null || ApprovedDelete(findForeignKeyColumn)
                                           select relProp;
            foreach (var relProp in relPropsToBeDeleted)
            {
                var relPropDeleter = new DMRelationshipPropertyDeleter(relProp);
                relPropDeleter.Delete();
            }
        }
/*

        private bool HasForeignKeyColumn(IEnumerable<DBForeignKeyColumn> dbForeignKeyColumns, IDMRelationshipProperty relationshipProperty)
        {
            DBForeignKeyColumn foundFKColumn = FindUCColumn(dbForeignKeyColumns, relationshipProperty);
            return foundFKColumn != null;
        }
*/

        public DBForeignKeyColumn FindForeignKeyColumn(IEnumerable<DBForeignKeyColumn> dbForeignKeyColumns, IDMRelationshipProperty relationshipProperty)
        {
            string ownerPropertyName = relationshipProperty.OwnerPropertyName;
            var relatedPropertyName = relationshipProperty.RelatedPropertyName;
            return dbForeignKeyColumns.FirstOrDefault(column 
                    => column.GetParentPropertyName() == ownerPropertyName 
                    && column.GetRelatedPropertyName() == relatedPropertyName);
        }
        public DMRelationshipProperty FindRelationshipProperty(IEnumerable<DMRelationshipProperty> relationshipProperties, DBForeignKeyColumn foreignKeyColumn)
        {
            string ownerPropertyName = foreignKeyColumn.GetParentPropertyName();
            string relatedPropertyName = foreignKeyColumn.GetRelatedPropertyName();
            return relationshipProperties.FirstOrDefault(prop 
                    => prop.OwnerPropertyName == ownerPropertyName
                    && prop.RelatedPropertyName == relatedPropertyName);
        }


        private bool ApprovedDelete(IMergableBo pkColumn)
        {
            return pkColumn.PendingChangeType == ChangeType.Delete && pkColumn.PendingChangeApproved;
        }
        public DMRelationshipProperty CreateRelationshipProperty(DMProperty parentProp, DMProperty relatedProp)
        {
/*            var parentPropertyName = parentProp == null ? "" : parentProp.PropertyName;
            var relatedPropertyName = relatedProp == null ? "" : relatedProp.PropertyName;*/
            DMRelationshipProperty relProp = new DMRelationshipProperty()
                {
                    OwnerProperty = parentProp,
                    RelatedProperty = relatedProp
                };
            //return ClassFactory.CreateRelPropDef(parentPropertyName, relatedPropertyName) as DMRelationshipProperty;
            return relProp;
        }

        private static string CreateStandardErrorMessage(DBForeignKey dbForeignKey)
        {
            return string.Format
                ("A relationship could not be created for the foreign key called '{1}' on table '{0}'",
                 dbForeignKey.ParentTable, dbForeignKey.ForeignKeyName);
        }


        private static void ValidateFKColumns(DBForeignKeyColumn foreignKeyColumnInfo, IList<string> errorMessages)
        {
            string standardStringMessage = CreateStandardErrorMessage(foreignKeyColumnInfo.ForeignKey);
            if (foreignKeyColumnInfo.ParentColumn == null)
            {
                errorMessages.Add(CreateParentColumnNullMessage(standardStringMessage));
                return;
            }
            DMProperty property = foreignKeyColumnInfo.ParentColumn.GetMappedDMProperty();
            if (property == null)
            {
                errorMessages.Add(CreatePropertyNullMessage(standardStringMessage, foreignKeyColumnInfo));
                return;
            }

            if (property.Class == null)
            {
                errorMessages.Add(CreatePropertyClassNullMessage(standardStringMessage, foreignKeyColumnInfo));
                return;
            }
            DBColumn relatedColumn = foreignKeyColumnInfo.RelatedColumn;
            if (relatedColumn == null)
            {
                errorMessages.Add(CreateRelatedColumnNullMessage(standardStringMessage, foreignKeyColumnInfo));
                return;
            }
            var relatedTable = relatedColumn.Table;
            if (relatedTable == null)
            {
                errorMessages.Add(CreateRelatedTableNullErrorMessage(standardStringMessage, foreignKeyColumnInfo));
                return;
            }
            var relatedClass = relatedTable.GetMappedDMClass();
            if (relatedClass == null)
            {
                errorMessages.Add(CreateRelatedClassNullErrorMessage(standardStringMessage, foreignKeyColumnInfo));
                return;
            }
            return;
        }

        public DMRelationship GetMappedRelationship(DBForeignKey dbForeignKey)
        {
            DMClass parentDMClass = dbForeignKey.ParentDMClass() as DMClass;
            DMClass relatedDMClass = dbForeignKey.RelatedDMClass() as DMClass;
            DMRelationship mappedDMRelationship = dbForeignKey.GetMappedDMRelationship();
            if (mappedDMRelationship == null)
            {
                mappedDMRelationship = relatedDMClass == null ? new DMRelationship(null) : new DMRelationship();

                mappedDMRelationship.MappedDBForeignKey = dbForeignKey;
                mappedDMRelationship.Cardinality = Cardinality.Single;
                mappedDMRelationship.DeleteAction = DeleteParentAction.DoNothing;
                mappedDMRelationship.RelationshipType = RelationshipType.Association;
                if(mappedDMRelationship.ReverseRelationship != null)
                {
                    mappedDMRelationship.ReverseRelationship.Cardinality = Cardinality.Multiple;
                    mappedDMRelationship.ReverseRelationship.DeleteAction = DeleteParentAction.Prevent;
                    mappedDMRelationship.RelationshipType = RelationshipType.Association;
                }
            }
            mappedDMRelationship.OwnerClass = parentDMClass;
            mappedDMRelationship.RelatedClass = relatedDMClass; 
            return mappedDMRelationship;
        }

        private static string CreateParentColumnNullMessage(string strandardStringMessage)
        {
            return strandardStringMessage + string.Format(
                " since the foreign key Parent column is null");
        }
        private static string CreatePropertyNullMessage(string strandardStringMessage, DBForeignKeyColumn foreignKeyColumnInfo)
        {
            return strandardStringMessage + string.Format(
                " since the foreign key column '{0}' is not mapped to a domain model class property",
                foreignKeyColumnInfo.ParentColumnName);
        }

        private static string CreatePropertyClassNullMessage(string strandardStringMessage, DBForeignKeyColumn foreignKeyColumnInfo)
        {
            return strandardStringMessage + string.Format(
                " since the foreign key column '{0}' do not exist on the domain model class",
                foreignKeyColumnInfo.ParentColumnName);
        }

        private static string CreateRelatedClassNullErrorMessage
            (string strandardStringMessage, DBForeignKeyColumn foreignKeyColumnInfo)
        {
            return strandardStringMessage + string.Format(
                " since the related table '{0}' for column '{1}' is not mapped to a domain model class",
                foreignKeyColumnInfo.RelatedTableName, foreignKeyColumnInfo.RelatedColumnName);
        }

        private static string CreateRelatedTableNullErrorMessage
            (string strandardStringMessage, DBForeignKeyColumn foreignKeyColumnInfo)
        {
            return strandardStringMessage + string.Format(
                " since the table for the related column '{0}' does not exist",
                foreignKeyColumnInfo.RelatedColumnName);
        }

        private static string CreateRelatedColumnNullMessage(string strandardStringMessage, DBForeignKeyColumn foreignKeyColumnInfo)
        {
            return strandardStringMessage + string.Format(
                " since the column that relates to the foreign key column called '{0}' does not exist",
                foreignKeyColumnInfo.ParentColumnName);
        }
    }
}